# coding: utf-8

import pyglet
import pyglet.gl as gl

from . import WindowProtocol
import abc
from math import *


def sign_var(x, f):
    """ Make the `f` the same sign of `x` with the exception when x is 0 the func returns 0.

    :param x: the sign indicator.
    :param f: the variable.
    :return: f with the same sign of x with exception that x is 0 then returns 0.
    """
    if x > 0:
        return f
    elif x < 0:
        return -f
    else:
        return 0


class Camera(object):
    def __init__(self, window, pos, look_at, head):
        """ Set up a camera.
        :param pos: the position.
        :param look_at: the position looking at.
        """
        super(Camera, self).__init__()
        self._pos = pos
        self._look_at = look_at
        self._head = head
        self._win = window

    @property
    def look_at(self):
        return self._look_at

    @look_at.setter
    def look_at(self, look_at):
        self._look_at = look_at

    def move(self, x=0.0, y=0.0):
        diff = [i - j for i, j in zip(self._pos, self._look_at)]
        ox = atan2(diff[2], diff[0])
        self._look_at = (self._look_at[0] + y * cos(ox), self._look_at[1], self._look_at[2] + y * sin(ox))
        self._pos = (self._pos[0] + y * cos(ox) + x * sin(ox), self._pos[1], self._pos[2] + y * sin(ox) + x * cos(ox))

    def rotate(self, x=0, y=0):
        diff = [i - j for i, j in zip(self._pos, self._look_at)]
        dist = sqrt(sum(i * i for i in diff))
        oy = acos(diff[1] / dist)
        ox = atan2(diff[2], diff[0])
        self._pos = (
            self._look_at[0] + dist * sin(oy + y) * cos(ox + x),
            self._look_at[1] + dist * cos(oy + y),
            self._look_at[2] + dist * sin(oy + y) * sin(ox + x + 2 * pi)
        )

    def scale(self, scale):
        diff = [i - j for i, j in zip(self._pos, self._look_at)]
        dist = sqrt(sum(i * i for i in diff))
        oy = acos(diff[1] / dist)
        ox = atan2(diff[2], diff[0])
        self._pos = (
            self._look_at[0] + dist * scale * sin(oy) * cos(ox),
            self._look_at[1] + dist * scale * cos(oy),
            self._look_at[2] + dist * scale * sin(oy) * sin(ox)
        )

    def setup(self, gl, glu, width, height):
        pos_x, pos_y, pos_z = self._pos
        look_at_x, look_at_y, look_at_z = self._look_at
        head_x, head_y, head_z = self._head
        width_height_ratio = width / height

        gl.glPushMatrix()

        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()

        # Set the perspective.
        glu.gluPerspective(45, width_height_ratio, 1, 1000)
        glu.gluLookAt(
            pos_x, pos_y, pos_z,  # Eye position.
            look_at_x, look_at_y, look_at_z,  # where we looking at.
            head_x, head_y, head_z  # where our head points into.
        )

        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()

        gl.glPopMatrix()


class Meta(pyglet.window._WindowMetaclass, abc.ABCMeta): pass


class GridWindow(pyglet.window.Window, WindowProtocol, metaclass=Meta):
    """
    A window to display.
    """

    def __init__(self, *args, **kwargs):
        pyglet.window.Window.__init__(self, *args, **kwargs)
        WindowProtocol.__init__(self)
        self._draw_queue = []
        self._camera = Camera(self,
                              pos=(0, 0, -10),
                              # pos=(0, 0, 0),
                              # look_at=(0, 20, 100),
                              look_at=(0, 0, -5),
                              # head=(0, -1, 0)
                              head=(0, 1, 0)
                              )
        # process the key states.
        self._keys = pyglet.window.key.KeyStateHandler()
        self.push_handlers(self._keys)

        def handle_directions(_):
            vec = 0.1
            if self._keys[pyglet.window.key.A]:
                self._camera.move(vec)
            if self._keys[pyglet.window.key.D]:
                self._camera.move(-vec)
            if self._keys[pyglet.window.key.S]:
                self._camera.move(y=vec)
            if self._keys[pyglet.window.key.W]:
                self._camera.move(y=-vec)
            self._camera.setup(gl, gl, self.width, self.height)

        self.time(60, handle_directions)
        self._key_listener = []
        # process the mouse.
        self.set_mouse_cursor(self.get_system_mouse_cursor(self.CURSOR_CROSSHAIR))

    @staticmethod
    def _draw_grid():
        gl.glColor3d(0.5, 0.5, 0.5)
        gl.glBegin(gl.GL_LINES)

        half_col = 5
        half_row = 5

        for i in range(-half_col, half_col + 1):
            gl.glVertex3d(i, 0, -half_row)
            gl.glVertex3d(i, 0, half_row)

        for j in range(-half_row, half_row + 1):
            gl.glVertex3d(-half_col, 0, j)
            gl.glVertex3d(half_col, 0, j)

        gl.glEnd()

    def key(self, listener):
        self._key_listener.append(listener)

    def on_key_press(self, symbol, modifiers):
        # # for amera.setup(gl, gl, self.width, self.height)
        # for key listeners
        for listener in self._key_listener:
            if callable(listener):
                listener(symbol)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        self._camera.rotate(x=-sign_var(dx, pi / 90))
        self._camera.setup(gl, gl, self.width, self.height)

    # def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
    #     self._camera.scale(signfix(scroll_y, 1.01))
    #     self._camera.setup(gl, gl, self.width, self.height)
    #     print(scroll_y)

    def time(self, dt, fun=None):
        if fun is None:
            def _wrap(func):
                pyglet.clock.schedule_interval(func, dt / 1000)

            return _wrap
        else:
            pyglet.clock.schedule_interval(fun, dt / 1000)

    def within_draw_process(self, func):
        self._draw_queue.append(func)

    def on_resize(self, width, height):
        self._camera.setup(gl, gl, width, height)
        return pyglet.event.EVENT_HANDLED

    def on_draw(self):

        gl.glPushMatrix()

        gl.glClear(gl.GL_DEPTH_BUFFER_BIT)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        # draw grid.
        self._draw_grid()

        # draw point cloud
        for func in self._draw_queue:
            func(gl)

        gl.glPopMatrix()

    def run(self):
        pyglet.app.run()
